/*
 *       sqlite api C++ 封装
 */
#pragma once
#include <sqlite3.h>

#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

#include "basic/trace.h"

namespace wheels {

class db_error : public std::runtime_error {
   public:
    db_error(const char *msg) : runtime_error(msg) {}
};

/// 用于执行ql语句 （无值返回 select语句不要使用这个）
class CSqliteExec {
   public:
    explicit CSqliteExec(const char *db_name) {
        int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE;
        sqlite3 *db = nullptr;
        auto ret = sqlite3_open_v2(db_name, &db, flags, nullptr);
        if (SQLITE_OK != ret) {
            trace_err("open sqlite db failed:[%s]\n", sqlite3_errstr(ret));
            return;
        }
        m_dbHndl = std::shared_ptr<sqlite3>(db, [](sqlite3 *p) {
            trace_info("close db");
            sqlite3_close(p);
        });
    }

    explicit CSqliteExec(std::shared_ptr<sqlite3> db) : m_dbHndl(db) {}
    CSqliteExec(const CSqliteExec &) = delete;
    const CSqliteExec &operator=(const CSqliteExec &) = delete;
    virtual ~CSqliteExec() {}

    int exec(const char *sql) {
        if (!m_dbHndl or !sql) {
            trace_err("sqlite handle or sql statement invalid \n");
            return SQLITE_ERROR;
        }

        char *errMsg = nullptr;
        auto ret = sqlite3_exec(m_dbHndl.get(), sql, nullptr, nullptr, &errMsg);
        std::shared_ptr<char> spErrMsg(errMsg, [](char *p) { sqlite3_free(p); });
        if (SQLITE_OK != ret) {
            trace_err("exec sql failed{%s}[%s]\n", sql, errMsg);
        }
        return ret;
    }

   protected:
    std::shared_ptr<sqlite3> m_dbHndl;
};

/// 动态绑定执行参数
class CSqlitePrepareExec final : public CSqliteExec {
   public:
    explicit CSqlitePrepareExec(const char *db_name) : CSqliteExec(db_name) {}
    explicit CSqlitePrepareExec(std::shared_ptr<sqlite3> db) : CSqliteExec(db) {}
    CSqlitePrepareExec(const CSqlitePrepareExec &) = delete;
    const CSqlitePrepareExec &operator=(const CSqlitePrepareExec &) = delete;
    ~CSqlitePrepareExec() {}

    template <typename T, typename... Args>
    int prepareExec(const char *sql, const T &t, const Args &...rest) {
        auto ret = initSqliteStatement(sql);
        if (SQLITE_OK != ret) {
            return ret;
        }

        ret = SQLITE_ERROR;
        try {
            ret = todo<sizeof...(rest) + 1>(t, rest...);
        } catch (const db_error &e) {
            trace_err("prepareExec failed: %s\n", e.what());
        }
        return ret;
    }

    template <typename T, typename... Args>
    int getRow(const char *sql, T &t, Args &...rest) {
        auto ret = initSqliteStatement(sql);
        if (SQLITE_OK != ret) {
            return ret;
        }

        if (SQLITE_ROW != sqlite3_step(m_spSqliteStmt.get())) {
            trace_err("not find row\n");
            return SQLITE_ERROR;
        }

        if (sizeof...(rest) != sqlite3_column_count(m_spSqliteStmt.get()) - 1) {
            trace_err("Invalid Param\n");
            return SQLITE_ERROR;
        }

        ret = SQLITE_ERROR;
        try {
            ret = toGet<sizeof...(rest)>(t, rest...);
        } catch (const db_error &e) {
            trace_err("getRow failed: %s\n", e.what());
        }
        return ret;
    }

   private:
    static constexpr int32_t SQLITE3_NOTMATH = ('s' << 24 | 'q' << 16 | 'n' << 8 | 'm');

   private:
    int initSqliteStatement(const char *sql) {
        if (nullptr == sql) {
            if (m_spSqliteStmt) {
                return sqlite3_reset(m_spSqliteStmt.get());
            } else {
                trace_err("Param Invalid\n");
                return SQLITE_ERROR;
            }
        }

        sqlite3_stmt *stmt = nullptr;
        auto ret = sqlite3_prepare_v2(m_dbHndl.get(), sql, -1, &stmt, nullptr);
        if (SQLITE_OK != ret) {
            trace_err("sqlite prepare failed{%s}[%s]\n", sql, sqlite3_errstr(ret));
        }
        m_spSqliteStmt.reset(stmt, [](sqlite3_stmt *p) {
            if (p) sqlite3_finalize(p);
        });
        return ret;
    }

    /// 动态绑定sql参数
    /// 以支持整形（bool char short int long longlong 有无符号）
    /// 浮点型 (float double 有无符号)
    /// char*
    /// string
    /// vector为目标
    template <typename T>
    int bindParam(int index, const T &t) {
        trace_err("not support bind the type{%s}\n", typeid(t).name());
        return SQLITE_ERROR;
    }

    /// 绑定整形
    template <typename T>
    int bindIntParam(int index, const T &t, std::true_type) {
        if (sizeof(T) == sizeof(int64_t)) {
            return sqlite3_bind_int64(m_spSqliteStmt.get(), index, t);
        }
        return sqlite3_bind_int(m_spSqliteStmt.get(), index, t);
    }
    template <typename T>
    int bindIntParam(int index, const T &t, std::false_type) {
        return SQLITE3_NOTMATH;
    }

    /// 绑定浮点形
    template <typename T>
    int bindFloatParam(int index, const T &t, std::true_type) {
        return sqlite3_bind_double(m_spSqliteStmt.get(), index, t);
    }
    template <typename T>
    int bindFloatParam(int index, const T &t, std::false_type) {
        return SQLITE3_NOTMATH;
    }

    inline int bindParam(int index, const char *p) {
        return sqlite3_bind_text(m_spSqliteStmt.get(), index, p, strlen(p), SQLITE_STATIC);
    }
    inline int bindParam(int index, const std::string &str) {
        return sqlite3_bind_text(m_spSqliteStmt.get(), index, str.c_str(), str.size(), SQLITE_STATIC);
    }
    inline int bindParam(int index, const std::vector<char> &data) {
        return sqlite3_bind_blob(m_spSqliteStmt.get(), index, data.data(), data.size(), SQLITE_STATIC);
    }

    /// 执行绑定
    template <int N>
    int todo() {
        auto ret = sqlite3_step(m_spSqliteStmt.get());
        while (SQLITE_ROW == ret) {
            ret = sqlite3_step(m_spSqliteStmt.get());
        }
        return SQLITE_DONE == ret ? SQLITE_OK : SQLITE_ERROR;
    }

    template <int N, typename T, typename... Args>
    int todo(const T &t, const Args &...rest) {
        auto ret = bindIntParam(N - sizeof...(rest), t, std::is_integral<T>());
        if (SQLITE3_NOTMATH == ret) {
            ret = bindFloatParam(N - sizeof...(rest), t, std::is_floating_point<T>());
        }

        if (SQLITE3_NOTMATH == ret) {
            ret = bindParam(N - sizeof...(rest), t);
        }

        if (SQLITE_OK != ret) {
            throw db_error("Bind param failed");
        }
        return todo<N>(rest...);
    }

    /// 获取select查询内容
    template <typename T>
    int getParam(int index, T &t) {
        trace_err("not support bind the type{%s}\n", typeid(t).name());
        return SQLITE_ERROR;
    }

    /// 获取整形
    template <typename T>
    int getIntParam(int index, T &param, std::true_type) {
        auto type = sqlite3_column_type(m_spSqliteStmt.get(), index);
        if (SQLITE_INTEGER != type) {
            trace_err("type not mathing{%d - int}\n", type);
            return SQLITE_ERROR;
        }

        if (sizeof(T) == sizeof(int64_t)) {
            param = sqlite3_column_int64(m_spSqliteStmt.get(), index);
        } else {
            param = sqlite3_column_int(m_spSqliteStmt.get(), index);
        }
        return SQLITE_OK;
    }

    template <typename T>
    int getIntParam(int index, T &param, std::false_type) {
        return SQLITE3_NOTMATH;
    }

    /// 获取浮点型
    template <typename T>
    int getFloatParam(int index, T &param, std::true_type) {
        auto type = sqlite3_column_type(m_spSqliteStmt.get(), index);
        if (SQLITE_FLOAT != type) {
            trace_err("type not mathing{%d - double}\n", type);
            return SQLITE_ERROR;
        }

        param = sqlite3_column_double(m_spSqliteStmt.get(), index);
        return SQLITE_OK;
    }

    template <typename T>
    int getFloatParam(int index, T &param, std::false_type) {
        return SQLITE3_NOTMATH;
    }

    int getParam(int index, std::string &param) {
        auto type = sqlite3_column_type(m_spSqliteStmt.get(), index);
        if (SQLITE3_TEXT != type) {
            trace_err("type not mathing{%d - string}\n", type);
            return SQLITE_ERROR;
        }

        auto data = sqlite3_column_text(m_spSqliteStmt.get(), index);
        param = std::string(reinterpret_cast<const char *>(data));
        return SQLITE_OK;
    }

    int getParam(int index, std::vector<char> &param) {
        auto type = sqlite3_column_type(m_spSqliteStmt.get(), index);
        if (SQLITE_BLOB != type) {
            trace_err("type not mathing{%d - string}\n", type);
            return SQLITE_ERROR;
        }

        auto data = sqlite3_column_blob(m_spSqliteStmt.get(), index);
        auto length = sqlite3_column_bytes(m_spSqliteStmt.get(), index);
        param = std::vector<char>(static_cast<const char *>(data), static_cast<const char *>(data) + length);
        return SQLITE_OK;
    }

    template <int N>
    int toGet() {
        return (sqlite3_step(m_spSqliteStmt.get()) == SQLITE_DONE) ? SQLITE_OK : SQLITE_ERROR;
    }

    template <int N, typename T, typename... Args>
    int toGet(T &t, Args &...rest) {
        auto ret = getIntParam(N - sizeof...(rest), t, std::is_integral<T>());
        if (SQLITE3_NOTMATH == ret) {
            ret = getFloatParam(N - sizeof...(rest), t, std::is_floating_point<T>());
        }

        if (SQLITE3_NOTMATH == ret) {
            ret = getParam(N - sizeof...(rest), t);
        }
        if (SQLITE_OK != ret) {
            throw db_error("get row failed");
        }
        return toGet<N>(rest...);
    }

   private:
    std::shared_ptr<sqlite3_stmt> m_spSqliteStmt;
};

}  // namespace wheels
